/* ******************************************************************
 * This file is part of the escal project and distributed under the
 * terms of the GNU General Public License Version 3
 * Copyright (C) 2008 Epocher (xijiaosdq@gmail.com)
 * See COPYING file for licensing information.
 *******************************************************************/

#include "curl_link_manager.h"
#include "escal.h"
#include "escal_settings.h"
#include "curl/curl.h"
#include "curl/types.h"
#include <QTime>
//#define QT_NO_DEBUG
using namespace Escal;

CurlLinkManager::CurlLinkManager(FileManager *fileManager, const QUrl &url,
		QObject *parent)
	:BaseLinkManager(parent)
{
	this->url = url;
	this->fileManager = fileManager;
	this->pieceIndex = 0;
	this->offset = 0;
	reconnect = false;
	gotPiece = false;
	error = false;
	quit = false;
}

CurlLinkManager::~CurlLinkManager(){
	stop();
	wait();
}

void CurlLinkManager::cleanUp(){
	static QMutex mutex;
	QMutexLocker lock(&mutex);
	if(gotPiece){
		fileManager->releasePiece(pieceIndex);
		gotPiece=false;
	}
};

void CurlLinkManager::stop(){
	quit = true;
	cleanUp();
};

int CurlLinkManager::writeToFileManager(char *buffer,size_t size){
	/* return value:
	 * true: you need to reconnect
	 * false: keep working
	 */
	enum{WRITE_OK=0, WRITE_ERROR, RECONNECT, QUIT, GET_INFO_ERROR,
		INIT_FILE_ERROR, GET_PIECE_FAILED};
	if(quit){
		emit writeLog(textCursor,QTime::currentTime()
				.toString(), tr("quiting ..."));

		return QUIT;
	}
	//long status;
	//curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
	//qDebug() << status;
	if(!gotInfo){
		emit writeLog(textCursor,QTime::currentTime().toString(),
				tr("start reading data..."));

		gotInfo = true;
	}
	if(fileManager->isNew()){
		emit writeLog(textCursor, QTime::currentTime().toString(),
			tr("file is new."));
		double length;
		curl_res = curl_easy_getinfo(curl, 
				CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
		qDebug() << "fileSize" << qint64(length);
		emit writeLog(textCursor, QTime::currentTime().toString(),
			tr("got filesize:%1").arg(qint64(length)));
		if(curl_res != CURLE_OK){
			qDebug() << "get file size failed";
			error = true;
			emit writeLog(textCursor,
				QTime::currentTime().toString(),
				tr("get info error, quiting ..."));
			return GET_INFO_ERROR;
		}
		else if(fileManager->initNewFile((qint64)length, true)){
			qDebug() << "init new file failed";
			error = true;
			emit writeLog(textCursor,
				QTime::currentTime().toString(),
				tr("initialize file error, quiting ..."));
			return INIT_FILE_ERROR;
		}
	}
	if(!gotPiece){
		offset=0;
		pieceIndex=fileManager->getPiece();
		if(pieceIndex<0){
			quit = true;
			emit writeLog(textCursor,
				QTime::currentTime().toString(),
				tr("failed to get piece, quiting ..."));
			return GET_PIECE_FAILED;
		}
		else if(pieceIndex>0){
			emit writeLog(textCursor, QTime::currentTime().toString(),
					tr("got piece:%1").arg(pieceIndex));
			gotPiece=true;
			emit writeLog(textCursor, QTime::currentTime().toString(),
					tr("need to reconnect"));
			return RECONNECT;
		}
		else{
			emit writeLog(textCursor, QTime::currentTime().toString(),
					tr("got piece:%1").arg(pieceIndex));
			gotPiece=true;
		}
	}
	QByteArray qBuffer(buffer, size);
	if(fileManager->write(pieceIndex, offset, gotPiece, qBuffer)){
		emit writeLog(textCursor,
				QTime::currentTime().toString(),
				tr("need to reconnect"));
		qDebug() << "need to reconnect";
		return WRITE_ERROR;
	}
	else{
		if(gotPiece)
			return WRITE_OK;
		else{
			emit writeLog(textCursor,
				QTime::currentTime().toString(),
				tr("lost piece, reconnecting..."));
			qDebug() << "not got piece";
			return RECONNECT;
		}
	}
}

size_t CurlLinkManager::CurlWriteFunc(void *buffer, size_t size, 
		size_t nmemb, void *stream)
{
	CurlLinkManager *curlLinkManager = (CurlLinkManager*)stream;
	if(!curlLinkManager->writeToFileManager((char*)buffer, size*nmemb)){
		return nmemb;
	}
	else{
		return 0;
	}
};

int CurlLinkManager::CurlDebugFunc(CURL *curl, curl_infotype type,
		char *buffer, size_t size, void *stream)
{
	if(type==CURLINFO_DATA_IN || type == CURLINFO_DATA_OUT){
		return 0;
	}
	CurlLinkManager *curlLinkManager = (CurlLinkManager*)stream;
	curlLinkManager->writeDebugInfo(buffer, size);
	return 0;
}

void CurlLinkManager::writeDebugInfo(char *buffer,size_t size){

	QByteArray logStr(buffer, size);
	QStringList logList = QString(logStr).split("\n");
	emit writeLog(textCursor, QTime::currentTime().toString(),
		logList[0]);
	for(int i=1;i<logList.size();i++){
		logList[i]=logList[i].simplified();
		if(logList[i].size()>0){
			emit writeLog(textCursor, QString(), logList[i]);
		}
	}
}

void CurlLinkManager::run(){
	error = false;
	quit = false;
	retry = 0;
	curl = curl_easy_init();
	if(!curl){
		error = true;
		qDebug() << "curl init failed!";
		emit writeLog(textCursor, QTime::currentTime().toString(),
				"curl init failed!");
		emit linkQuited(this, error);
		return;
	}
	if(!fileManager->isNew()){
        	if(!gotPiece && ((pieceIndex = fileManager->getPiece()) <0)){
			emit writeLog(textCursor,QTime::currentTime()
				.toString(), "no piece got, quiting...");
	    		error = true;
			quit = true;
		}
		else{
			gotPiece = true;
			emit writeLog(textCursor,QTime::currentTime()
				.toString(), tr("got piece: %1")
				.arg(pieceIndex));
		}
	}
	else{
		emit writeLog(textCursor,QTime::currentTime()
				.toString(), tr("assuming piece index to 0."));
	}
	urlByteArray = url.toEncoded();
	while(!quit){
		gotInfo=false;
		emit writeLog(textCursor, QTime::currentTime()
			.toString(), tr("start downloading..."));
		curl_easy_reset(curl);
		curl_easy_setopt(curl, CURLOPT_URL, urlByteArray.constData());
		curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlWriteFunc);
		curl_easy_setopt(curl, CURLOPT_WRITEDATA, this);
		curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugFunc);
		curl_easy_setopt(curl, CURLOPT_DEBUGDATA, this);
		curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
		curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
		if(settings->value("Network/UseProxy").toBool()){
			QByteArray addr;
			QByteArray port;
			addr = settings->value("Network/ProxyServerAddr")
				.toByteArray();
			port = settings->value("Network/ProxyServerPort")
				.toByteArray();
			addr.append(":").append(port);
			qDebug() << addr;
			curl_easy_setopt(curl, CURLOPT_PROXY, addr.constData());
		}
		if(gotPiece){
			curl_easy_setopt(curl, CURLOPT_RESUME_FROM_LARGE,
					pieceIndex * PieceSize + offset);
			emit writeLog(textCursor,QTime::currentTime()
				.toString(), tr("range: %1")
				.arg(pieceIndex * PieceSize + offset));
		}
		qDebug() << "start download" << this;
		curl_res = curl_easy_perform(curl);
		if(curl_res==CURLE_WRITE_ERROR){
			qDebug() << "write error";
			retry++;
			if(retry>30){
				error = true;
				break;
			}
			else
				continue;
		}
		else if(curl_res != CURLE_OK){
			qDebug() << "curl_res != CURLE_OK";
			error = true;
			break;
		}
		else{
			if(fileManager->isNew()){
				fileManager->initNewFile(0,false);
				break;
			}
		}
		sleep(3);
	}
	curl_easy_cleanup(curl);
	emit linkQuited(this, error);
}
